/*
 * Copyright (C) 2014 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3;

import java.io.IOException;
import java.util.List;

import okhttp3.internal.connection.RealConnection;
import okhttp3.internal.connection.StreamAllocation;
import okhttp3.internal.http.HttpCodec;

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
    Response intercept(RealInterceptorChain chain) throws IOException;

    final class RealInterceptorChain {
        private final List<Interceptor> interceptors;
        private final StreamAllocation streamAllocation;
        private final HttpCodec httpCodec;
        private final RealConnection connection;
        private final int index;
        private final Request request;
        private int calls;

        public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
                                    HttpCodec httpCodec, RealConnection connection, int index, Request request) {
            this.interceptors = interceptors;
            this.connection = connection;
            this.streamAllocation = streamAllocation;
            this.httpCodec = httpCodec;
            this.index = index;
            this.request = request;
        }

        public RealConnection connection() {
            return connection;
        }

        public StreamAllocation streamAllocation() {
            return streamAllocation;
        }

        public HttpCodec httpStream() {
            return httpCodec;
        }

        public Request request() {
            return request;
        }

        public Response proceed(Request request) throws IOException {
            return proceed(request, streamAllocation, httpCodec, connection);
        }

        public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
                                RealConnection connection) throws IOException {
            if (index >= interceptors.size()) throw new AssertionError();

            calls++;

            // If we already have a stream, confirm that the incoming request will use it.
            if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
                throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                        + " must retain the same host and port");
            }

            // If we already have a stream, confirm that this is the only call to chain.proceed().
            if (this.httpCodec != null && calls > 1) {
                throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
                        + " must call proceed() exactly once");
            }

            // Call the next interceptor in the chain.
            RealInterceptorChain next = new RealInterceptorChain(
                    interceptors, streamAllocation, httpCodec, connection, index + 1, request);
            Interceptor interceptor = interceptors.get(index);
            Response response = interceptor.intercept(next);

            // Confirm that the next interceptor made its required call to chain.proceed().
            if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
                throw new IllegalStateException("network interceptor " + interceptor
                        + " must call proceed() exactly once");
            }

            // Confirm that the intercepted response isn't null.
            if (response == null) {
                throw new NullPointerException("interceptor " + interceptor + " returned null");
            }

            return response;
        }
    }
}
